#include "./sensor.h"
#include "../cfg/app_info.h"
#include "../rtx/rtx_os.h"
#include "../utils/log.h"
#include "../xlib/adc.h"
#include "../xlib/dma.h"
#include "../xlib/gpio.h"


namespace driver {

Sensor sensor;

using namespace stm32f1;

namespace sensor_ns {

using namespace gpio;

using WATER_LEVEL_LOW = Cfg<PC<1>, Mode::INPUT_FLOAT>;
using WATER_LEVEL_HIGH = Cfg<PC<0>, Mode::INPUT_FLOAT>;
using LEVEL_RESERVE = Cfg<PC<2>, Mode::INPUT_FLOAT>;
using LEVEL_RESERVE2 = Cfg<PC<3>, Mode::INPUT_FLOAT>;
using FLOW_METER = Cfg<PA<12>, Mode::INPUT_FLOAT>;
using FLOW_METER2 = Cfg<PA<8>, Mode::INPUT_FLOAT>;
using BOWL_CHECK = Cfg<PC<4>, Mode::INPUT_FLOAT>;

using KPA = Cfg<PA<0>, Mode::INPUT_ANALOG>;
using GO_SWITCH = Cfg<PA<11>, Mode::INPUT_FLOAT>;
using LEAK1 = Cfg<PC<5>, Mode::INPUT_FLOAT>;
using LEAK2 = Cfg<PB<12>, Mode::INPUT_FLOAT>;

using SPI_SCK = Cfg<PA<5>, Mode::OUT_50MHz_AF_PP>;
using SPI_MISO = Cfg<PA<6>, Mode::INPUT_FLOAT>;
using WATER_CS = Cfg<PB<11>, Mode::OUT_50MHz_PP>;
using STEAM_CS = Cfg<PB<10>, Mode::OUT_50MHz_PP>;

using KPA_ADC = adc::Adc1;
using KPA_DMA = dma::DmaSel<KPA_ADC::BASE_VAL>::CHAN;

static volatile uint16_t flowCount = 0;
static volatile uint16_t kpaValue = 0;

static void flow_init() {
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    EXTI->IMR |= EXTI_IMR_MR12;
    EXTI->FTSR |= EXTI_FTSR_TR12;
    AFIO->EXTICR[3] = AFIO->EXTICR[3] & (~0x0F);
    NVIC_EnableIRQ(EXTI15_10_IRQn);
}

static void spi_init() {
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    SPI1->CR1 = 0x00;
    SPI1->CR2 = 0x00;
    SPI1->CR1 = SPI_CR1_DFF | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE |
                SPI_CR1_MSTR | (3 << 3);
    WATER_CS::set();
    STEAM_CS::set();
}

static uint32_t spi_read_u32() {
    uint32_t h, l;

    while ((SPI1->SR & SPI_SR_TXE) == 0x00)
        ;
    SPI1->DR = 0;
    while ((SPI1->SR & SPI_SR_RXNE) == 0x00)
        ;
    h = SPI1->DR;

    while ((SPI1->SR & SPI_SR_TXE) == 0x00)
        ;
    SPI1->DR = 0;
    while ((SPI1->SR & SPI_SR_RXNE) == 0x00)
        ;
    l = SPI1->DR;

    return (h << 16) + l;
}

static bool convert(int16_t &value, uint32_t temp) {
    constexpr uint32_t FAULT_FLAG = 0x07 | (0x01 << 16);

    if (temp & FAULT_FLAG) {
        return false;
    }
    uint16_t t = (temp >> 18) & 0x3FFF;
    if (t & 0x2000) {
        value = -((0x4000 - t) >> 2);
    } else {
        value = t >> 2;
    }
    return true;
}

} // namespace sensor_ns

using namespace sensor_ns;

void Sensor::init() {

    rcc::enable<WATER_LEVEL_HIGH, WATER_LEVEL_LOW, FLOW_METER, KPA, GO_SWITCH,
                LEAK1, LEAK2, SPI_SCK, SPI_MISO, WATER_CS, STEAM_CS, KPA_ADC,
                KPA_DMA, BOWL_CHECK>();
    gpio::config<WATER_LEVEL_HIGH, WATER_LEVEL_LOW, FLOW_METER, KPA, GO_SWITCH,
                 LEAK1, LEAK2, SPI_SCK, SPI_MISO, WATER_CS, STEAM_CS,
                 BOWL_CHECK>();
    spi_init();
    flow_init();
    adc::config<KPA_ADC, 0>(&kpaValue);
}

uint16_t Sensor::flow_count() const { return flowCount; }
void Sensor::flow_reset() { flowCount = 0; }
uint16_t Sensor::flow_volume() const {
    using namespace cfg;
    return flowCount * 1000 / appInfo->flow_count_1000L;
}

void Sensor::read_temp() {
    WATER_CS::clr();
    m_water_temp = spi_read_u32();
    WATER_CS::set();

    osDelay(50);

    STEAM_CS::clr();
    m_steam_temp = spi_read_u32();
    STEAM_CS::set();

    //  println("water:%08x steam:%08x", m_water_temp, m_steam_temp);

    m_water_temp_ok = convert(m_water_temp_value, m_water_temp);
    m_steam_temp_ok = convert(m_steam_temp_value, m_steam_temp);
}

bool Sensor::is_water_level_high() const { return WATER_LEVEL_HIGH::read(); }
bool Sensor::is_water_level_low() const { return WATER_LEVEL_LOW::read(); }
bool Sensor::is_go_switch() const { return !GO_SWITCH::read(); }
bool Sensor::is_leak1() const { return LEAK1::read(); }
bool Sensor::is_leak2() const { return LEAK2::read(); }
uint16_t Sensor::kpa() const {
    float val = kpaValue * 0.5323f;
    if (val < 388.25f) {
        return 0;
    }
    return val - 388.25f;
}
bool Sensor::is_bowl_checked() const { return !BOWL_CHECK::read(); }

} // namespace driver

using namespace driver;

extern "C" void EXTI15_10_IRQHandler() {
    if (EXTI->PR & EXTI_PR_PR12) {
        EXTI->PR = EXTI_PR_PR12;
        flowCount += 1;
    }
}
